<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  <title>关于分布式(转载学习) | HankChan&#39;s Blog</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="description" content="作者：陈叶皓（携程邮轮研发部软件架构师） 我的架构师同事问我：“为什么你总说要在服务层实现读写分离，我们已经在数据库实现了读写分离，是不是已经够用”。以下是我的解释 在做网站性能优化的时候，我常常忘记还有数据库读写分离这件事，因为数据库读写分离，对性能带来的提高太有限了，实际上，就是一倍（一台服务器变成两台服务器）。当你的网站业务发展，如果从无到有地使用数据库读写分离，提高了一倍的服务能力，你很快">
<meta property="og:type" content="article">
<meta property="og:title" content="关于分布式(转载学习)">
<meta property="og:url" content="http://hexo.hankchan.cn/2016/09/02/e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0/index.html">
<meta property="og:site_name" content="HankChan&#39;s Blog">
<meta property="og:description" content="作者：陈叶皓（携程邮轮研发部软件架构师） 我的架构师同事问我：“为什么你总说要在服务层实现读写分离，我们已经在数据库实现了读写分离，是不是已经够用”。以下是我的解释 在做网站性能优化的时候，我常常忘记还有数据库读写分离这件事，因为数据库读写分离，对性能带来的提高太有限了，实际上，就是一倍（一台服务器变成两台服务器）。当你的网站业务发展，如果从无到有地使用数据库读写分离，提高了一倍的服务能力，你很快">
<meta property="og:updated_time" content="2017-05-26T02:13:23.615Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="关于分布式(转载学习)">
<meta name="twitter:description" content="作者：陈叶皓（携程邮轮研发部软件架构师） 我的架构师同事问我：“为什么你总说要在服务层实现读写分离，我们已经在数据库实现了读写分离，是不是已经够用”。以下是我的解释 在做网站性能优化的时候，我常常忘记还有数据库读写分离这件事，因为数据库读写分离，对性能带来的提高太有限了，实际上，就是一倍（一台服务器变成两台服务器）。当你的网站业务发展，如果从无到有地使用数据库读写分离，提高了一倍的服务能力，你很快">
  
    <link rel="alternate" href="/atom.xml" title="HankChan&#39;s Blog" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
  

</head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">HankChan&#39;s Blog</a>
      </h1>
      
        <h2 id="subtitle-wrap">
          <a href="/" id="subtitle">生活就像海洋</a>
        </h2>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="搜索"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="http://hexo.hankchan.cn"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main"><article id="post-e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2016/09/02/e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0/" class="article-date">
  <time datetime="2016-09-02T11:51:45.000Z" itemprop="datePublished">2016-09-02</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/categories/分布式/">分布式</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="article-title" itemprop="name">
      关于分布式(转载学习)
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>作者：陈叶皓（携程邮轮研发部软件架构师）</p>
<p>我的架构师同事问我：“为什么你总说要在服务层实现读写分离，我们已经在数据库实现了读写分离，是不是已经够用”。以下是我的解释</p>
<p>在做网站性能优化的时候，我常常忘记还有数据库读写分离这件事，因为数据库读写分离，对性能带来的提高太有限了，实际上，就是一倍（一台服务器变成两台服务器）。当你的网站业务发展，如果从无到有地使用数据库读写分离，提高了一倍的服务能力，你很快就需要想新的优化方案。实际上，数据库的读写分离，更像是数据安全的一个副产品，用一台数据库服务器不安全（怕数据丢失），用一台服务器作为备份，既然有了两台服务器，就充分利用吧，于是有了“读写分离”，提高一倍也是好的。</p>
<p>于是，能够十倍百倍提高性能的方案出现了，缓存加服务器集群，这是最常用且有效的提高网站访问量的设计。使用共享缓存（memcached，redis）可以获得十到几十倍的性能提升，使用进程内缓存，可以得到百倍的性能提升；集群中增加一倍的服务器，可以增加一倍的计算能力，服务更多的并发请求。等一下，上面所说的方案，其实只对“读”操作才有效，对“写”操作可以说是毫无用处。</p>
<p>那么有什么办法可以提高“写”操作的性能，在架构部署的设计方面，我的答案是，“没有”。</p>
<p>从硬件入手，可以使用SSD硬盘。愿意替换底层数据库，可以使用hbase或者cassandra，都不在今天讨论的范围。我想说的是，既然使用缓存和增加服务器，对于“写”操作没有优化作用，在一开始，“写”操作相关的服务，就不该和“读”操作一起，被分配到数量庞大的计算机集群里。</p>
<p>想象这样的架构设计，我有一个“读”服务的集群，一共4台服务器，我有一台“写”服务器（另一台备用，故障时切换）。当我的网站访问量上升，我增加“读”服务器集群到8台，简单就能应付问题。因为“读”服务是状态无关的，增加到100台也不会带来错误的数据，这是一个重要的思想，状态无关的服务，才可以放心地水平扩展，事实上，状态无关的服务，通常只有“读”服务。</p>
<p>那么当“写”服务撑不住的时候，怎么办，嗯。。。总会有办法，反正不是加缓存或者是使用集群，这个可以做架构师面试题。</p>
<p>然后我解释一下为什么不该在集群里面运行“写”服务，我把“写”服务分为两种。</p>
<ol>
<li>和“状态”（可能发生冲突的情形）弱相关，比如用户提供内容（UGC）的操作，每个用户提交自己的评论，或者发布自己的微博，不太容易发生冲突。对于这类“写”服务，部署在集群里面勉强可行，虽然没带来什么好处，但也没有引入错误<br>`</li>
<li><pre>`  和“状态”（可能发生冲突的情形）强相关，比如包含库存操作的电商网站，上千人“秒杀”热门商品，允许这样的操作在集群内并发，是架构师自己作死的节奏啊<br>明白了这个道理，你就知道我之前为什么说是“一台”写服务器，只有一台服务器，才可以保证在“秒杀”场景下，不会在没有库存的情况下继续售卖成功。</pre></li>
</ol>
<p>细心的读者（嗯，就是你）会继续追问，在一台服务器的情况下，现在都是多核并发编程，保证串行操作也不是容易的事啊。问得太好了，我这大半年写的系列文章，都是为了解决这个问题，你需要的是actor模型。异步编程加上进程内的消息队列，可以高效地对并发操作进行串行的处理。</p>
<p>结论，使用服务器集群提高性能只对“读”服务有效，对“写”服务无效，“写”服务器应该使用主/从模式，同一时间只使用一台服务器。在“写”服务器内部，使用支持actor模型的编程语言，保证关键操作的串行。最后老生常谈，支持actor模型的编程语言是：Erlang，Go，Scala，F#</p>
<p>是不是要在标题的“作用”之前加上“重要”两个字，我犹豫了一下，zookeeper提供的功能是如此的重要，以至于如果你在应用中不使用它，早晚也会在你的应用中去实现zookeeper 的功能，所以，zookeeper值得你花（一点）时间去掌握。</p>
<p>zookeeper是为了“分布式”而诞生的，我反复在说“分布式”，并不是赶潮流，而是被潮流推着向前。在任何互联网生产应用中，哪怕你的公司规模小，访问量用一台服务器足够应付，仍然不能容忍当服务器故障时，没有备用的服务器可切换，这个称为“防止单点故障”，因为你至少要用两台服务器来防止单点故障，所以你已经在“分布式”的服务环境里。</p>
<p>我们来回顾上一篇提到的“为什么要在服务层设计读写分离”，我把应用层的通用服务分为“读”服务和“写”服务，“读”服务用集群来实现高可用高性能，而“写”服务用单台服务器来保证事务顺序执行。</p>
<p>那么，“单台服务器”听上去好危险的样子，于是今天的主角登场，我们需要zookeeper。</p>
<p>你也许听到过，这种应用场景叫做master/slave，或者我更喜欢称为主/备模式，在这种场景下，我有两台服务器（主和备），任何情况下，只有“主”在工作，“备”是在主出现故障时，接替“主”来提供服务。在zookeeper的支持下，这一过程是这样实现的， Zookeeper提供目录和节点的服务，当我的两台服务器启动时，会在zookeeper的指定目录下创建对应自己的临时节点（这个过程称为“注册”），所谓临时节点，是靠心跳（定时向zookeeper服务器发送数据包）维系，当服务器出现故障（无法向zookeeper服务器发送数据包），zookeeper会删除临时节点。服务器向zookeeper注册时，zookeeper会分配序列号，我们认为序列号小的那个，就是“主”，序列号大的那个，就是“备”。</p>
<p>当我们的客户端（通常是web server）需要访问“写”服务时，需要连接zookeeper，获得指定目录下的临时节点列表，也就是已经注册的服务器信息，获得序列号小的那台“主”服务器的地址，进行后续的访问操作。以达到“总是访问主服务器”的目的。</p>
<p>当“主”服务器发生故障，zookeeper从指定目录下删除对应的临时节点，同时可以通知关心这一变化的所有客户端，高效且迅速的传播这一信息，你想一想，如果不是使用zookeeper，要自己实现这个功能，可没。。。那么简单（不许唱！）</p>
<p>我们为了消除单点故障而使用的主/备模式依赖zookeeper，那么zookeeper可不能有单点故障，所以zookeeper在诞生的时候，就是用集群的模式工作，用多台服务器来消除自身的单点故障隐患，怎么样，无可挑剔吧。</p>
<p>总结，在多核并行计算模式下，我认定基于消息传递的actor模型（源自erlang）是正确的编程方式，在actor模型下，可以简单实现基于服务层的串行操作，保证“写”操作的完整和一致。使用actor模型，需要用主/备的部署架构来消除单点故障，实现主/备的部署架构，最简单可靠的方法是使用zookeeper。所以我现在的软件架构是这么推导出来的高并发需求 -&gt; 异步计算 (使用actor model) -&gt; master/slave (使用zookeeper)</p>
<p>我们先来回顾一下之前提到过的知识点， 1.在一台电脑上，使用异步编程可以提高cpu的使用效率 2.使用Actor模型，实现同一台电脑上，在并发环境下的串行操作，保证事务执行的正确 3.在多服务器环境下，actor模型配合Zookeeper，可以实现在多服务器环境下的串行操作，保证事务执行正确 4.对应用进行读写分离的设计，做到“写服务”（有状态）执行正确，同时又能方便地（增加服务器）提高“读服务”（无状态）的性能</p>
<p>所以，在服务实现的内部，异步已经无处不在，今天我来讲服务的消费者，也就是浏览器客户端，为什么也需要使用异步模式。</p>
<p>在Joe Armstrong创造erlang的时候，他假设网络通讯是不可靠的，外部服务的响应时间是不可靠的，所以erlang里面只有异步调用，同步调用是用异步来模拟。Erlang软件的运行，不会因为任何的外部操作而阻塞cpu。</p>
<p>而到了互联网时代，Joe Armstrong的假设依旧成立，网络通讯不可靠，外部服务不可靠。设计同步调用的应用架构，从一开始就引入了巨大的风险。举个简单的电商下单的例子，步骤如下， 1.浏览器发送下单请求到网页服务器（web server） 2.网页服务器发送请求到应用服务器 3.应用服务器检查库存，锁定库存，生成订单。通知网页服务器下单成功 4.网页服务器把下单结果传送到用户的浏览器</p>
<p>在这个场景下，步骤3最为耗时，有时需要超过1秒。对于步骤2，我们有很多方法来实现异步操作，如果不能实现，那网页服务器被应用服务器的响应所阻塞，吞吐量将急剧下降。</p>
<p>今天我们主要讨论步骤1，用户在下单，期望看到下单的结果，这是一个典型的同步操作，随着并发量上升，服务器的响应时间可能会超过30秒，最终造成浏览器的超时，用户什么也看不到，这是最坏的结果。我们来把这个同步调用，用异步来模拟， 1.浏览器发送下单请求到网页服务器（web server） 2.网页服务器发送请求到应用服务器 3.a)应用服务器生成订单号，把订单号返回网页服务器 b)应用服务器检查库存，锁定库存，把订单状态改成“下单成功” 4.浏览器收到订单号，采用ajax方式，每隔2秒，请求网页服务器，查询订单状态，直到获得“下单成功”的状态，跳转到下单成功页面</p>
<p>在这个异步流程中，步骤3.b和步骤4在时间上是并行执行的，但是，还记得我们的“读写分离”设计吗，步骤3访问的是“写”服务，步骤4访问的是“读”服务，这两个服务可以独立优化，不会成为对方的瓶颈。</p>
<p>使用浏览器的异步访问还带来额外的好处，在异步架构下，网页服务器和应用服务器的响应都非常迅速。原先的耗时请求“请帮我下单，并告诉我下单结果”被拆分成不耗时的请求“帮我下单”（一次）和“刚才的下单成功了吗”（一次或多次）。</p>
<p>如果使用了老式的网页服务器，不支持异步请求应用服务器，这样还能一举解决网页服务器访问应用服务器阻塞的问题。</p>
<p>在互联网高并发的状态改变（写）操作，把业务流程设计成异步是上策，比如大众点评的退款操作，系统会告诉用户“已收到您的退款请求，会在x个工作日能把钱退到您的账户”，这样的异步业务流程，对于系统毫无压力。有时出于用户体验不得不采用同步流程，比如上面说的下单流程，如果一开始设计成用异步流程来模拟，有两个好处 1.不会因为并发量突然上升而触发浏览器超时 2.可以对“读”，“写”服务独立优化 另外，在ajax异步操作时，还可以在浏览器绘制动画来安抚用户焦躁的情绪，不要说我没告诉你~</p>
<p>用异步流程模拟同步流程的补充说明，在浏览器内使用ajax轮询下单结果，是不得以。在服务器可以主动访问客户端的环境下，应该总是优先考虑回调的方式。浏览器是特殊的环境，服务器访问浏览器有时有困难，所以才使用比较保险的轮询模式。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://hexo.hankchan.cn/2016/09/02/e5-85-b3-e4-ba-8e-e5-88-86-e5-b8-83-e5-bc-8f-e8-bd-ac-e8-bd-bd-e5-ad-a6-e4-b9-a0/" data-id="cj359k3uj0003fwv7044ygxrd" class="article-share-link">Share</a>
      
      
    </footer>
  </div>
  
    
<nav id="article-nav">
  
    <a href="/2016/09/23/mybatis-e7-bb-93-e5-90-88spring-e7-9a-84-e5-85-b6-e4-b8-ad-e4-b8-80-e7-a7-8d-e5-bc-80-e5-8f-91-e6-80-9d-e8-b7-af/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Newer</strong>
      <div class="article-nav-title">
        
          Mybatis结合Spring的其中一种开发思路
        
      </div>
    </a>
  
  
    <a href="/2016/08/16/e9-80-9a-e8-bf-87-e6-90-ad-e5-bb-ba-e6-9c-ac-e5-9c-b0-e6-9c-8d-e5-8a-a1-e5-99-a8-e8-8e-b7-e5-8f-96webstorm-e7-9a-84-e4-bd-bf-e7-94-a8-e6-9d-83-e9-99-90/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">Older</strong>
      <div class="article-nav-title">通过搭建本地服务器获取Webstorm的使用权限</div>
    </a>
  
</nav>

  
</article>

</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/Docker/">Docker</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/">Eclipse</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/Mybatis/">Mybatis</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/Spring/">Spring</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Eclipse/分布式/">分布式</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Git/">Git</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/">Linux</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Linux/Nginx/">Nginx</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/">Mybatis</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/Spring/">Spring</a><ul class="category-list-child"><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/Spring/SpringMVC/">SpringMVC</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Mybatis/SpringMVC/">SpringMVC</a></li></ul></li><li class="category-list-item"><a class="category-list-link" href="/categories/Nginx/">Nginx</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/Spring/">Spring</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/SpringMVC/">SpringMVC</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/分布式/">分布式</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/日志管理/">日志管理</a></li><li class="category-list-item"><a class="category-list-link" href="/categories/未分类/">未分类</a></li></ul>
    </div>
  </div>


  
    

  
    
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/03/">三月 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/01/">一月 2017</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/10/">十月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/09/">九月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/08/">八月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/07/">七月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/06/">六月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/05/">五月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/04/">四月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/03/">三月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/02/">二月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/01/">一月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/12/">十二月 2015</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2017/03/13/docker-e5-be-ae-e6-9c-8d-e5-8a-a1-e5-ae-b9-e5-99-a8-e5-8c-96-e5-bc-80-e5-8f-91-e6-8e-a2-e7-b4-a2/">Docker-微服务容器化开发探索</a>
          </li>
        
          <li>
            <a href="/2017/01/20/drds-e7-bb-93-e5-90-88mybatis-e8-a7-a3-e5-86-b3-e6-96-b9-e6-a1-88-e4-b8-8e-e8-a7-84-e8-8c-83/">DRDS结合Mybatis解决方案与规范</a>
          </li>
        
          <li>
            <a href="/2017/01/03/springmvc-thymeleaf-e5-bc-80-e5-8f-91-e6-8c-87-e5-8d-97-demo/">SpringMVC-Thymeleaf-开发指南-Demo</a>
          </li>
        
          <li>
            <a href="/2016/10/16/nginx-e4-bb-a3-e7-90-86-e6-9c-8d-e5-8a-a1-e5-99-a8-e6-96-b9-e5-bc-8f-e8-a7-a3-e5-86-b3-e5-ae-9e-e9-99-85-e5-bc-80-e5-8f-91-e4-b8-adajax-e8-af-b7-e6-b1-82-e7-9a-84-e8-b7-a8-e5-9f-9f-e9-97-ae-e9-a2-98/">Nginx代理服务器方式解决实际开发中Ajax请求的跨域问题</a>
          </li>
        
          <li>
            <a href="/2016/10/11/e5-b7-a5-e5-8e-82-e6-a8-a1-e5-bc-8f-e5-92-8c-e5-8d-95-e4-be-8b-e6-a8-a1-e5-bc-8f-e7-bb-93-e5-90-88-e5-ba-94-e7-94-a8-e7-9a-84-e5-9c-ba-e6-99-af-e6-80-9d-e8-80-83/">工厂模式和单例模式结合应用的场景思考</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2017 Hank Chan<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>

  </div>
</body>
</html>